package com.chenjia.proto.converter;

import com.chenjia.proto.converter.utils.FieldUtils;
import com.esotericsoftware.reflectasm.MethodAccess;
import com.google.protobuf.Message;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * 状态化的 ReflectASM 管理器，用于缓存 {@link Class}-{@link MethodAccess} 映射，method-methodIndex 映射。
 */
class ReflectAsmManager {

    /**
     * 原始Class：增强Class
     */
    private static final ConcurrentMap<Class, MethodAccess> methodAccessMap = new ConcurrentHashMap<>();

    // 映射关系缓存（SourceClass，TargetClass）:{getterName:setterName}

    /**
     * {@link Message} 转 pojo 映射
     */
    private static final ConcurrentMap<Pair<? extends Message, ?>, Map<Integer, Integer>> toPojoMap = new ConcurrentHashMap<>();

    /**
     * pojo 转 {@link Message} 映射
     */
    private static final ConcurrentMap<Pair<?, ? extends Message.Builder>, Map<Integer, Integer>> toMessageMap = new ConcurrentHashMap<>();

    public static MethodAccess getMethodAccess(Class clazz) {
        return methodAccessMap.computeIfAbsent(clazz, MethodAccess::get);
    }

    public static Map<Integer, Integer> getToPojoModel(Pair<? extends Message, ?> pair) {
        return toPojoMap.computeIfAbsent(pair, ReflectAsmManager::buildToPojoModel);
    }

    /**
     * 将互转的两个 class 解析为 getter：setter 键值对。
     * <p>
     * 但是这里并不是保存 {@link Method}，因为反射调用性能较差，这里改用 ReflectAsm 库。
     * 所以这里保存的是对应方法在 {@link MethodAccess} 中的 MethodIndex。
     */
    private static Map<Integer, Integer> buildToPojoModel(Pair<? extends Message, ?> pair) {
        System.out.println("buildToPojoModel = " + pair);
        final Class<? extends Message> messageClass = pair.key;
        final Class<?> pojoClass = pair.value;
        final MethodAccess messageAccess = getMethodAccess(messageClass);
        final MethodAccess pojoAccess = getMethodAccess(pojoClass);

        // 从 pojoClass（targetClass）中获取所有符合条件的（注解控制） Field

        // TODO 注解控制逻辑：取别名等
        // pojo set，message get
        // final String[] methodNames = pojoAccess.getMethodNames();
        // pojoAccess.getMethodNames() 可以获取到所有的方法，并且通过 index 可以随机访问到对应方法的参数类型，但是没有更多的信息了。


        final List<Field> fields = getFields(pojoClass);
        Map<Integer, Integer> indexMap = new HashMap<>();
        for (Field field : fields) {
            if (Modifier.isStatic(field.getModifiers())) continue;
            // 以此构建出适合于 pojo 与 message 的 setter & getter
            final String protobufGetterName = FieldUtils.createProtobufGetterName(field.getName(), field.getType());
            try {
                final Method messageGetMethod = messageClass.getMethod(protobufGetterName);
                final String pojoSetterName = FieldUtils.createDomainSetterName(field.getName());
                final Method pojoSetMethod = pojoClass.getMethod(pojoSetterName, field.getType());
                // 当两个方法都存在时，保存对应方法在 MethodAccess 中的 index，优化后续访问性能；
                // 另外，为防止 Java 优化死代码调用，需要重复 Method.getName，避免优化项
                indexMap.put(messageAccess.getIndex(messageGetMethod.getName()), pojoAccess.getIndex(pojoSetMethod.getName()));
            } catch (NoSuchMethodException e) {
                // continue
                // 此处应该有 debug 日志
            }
        }
        return indexMap;
    }


    private static List<Field> getFields(final Class clazz) {
        List<Field> fields = new ArrayList<>(Arrays.asList(clazz.getDeclaredFields()));
        Class superClazz = clazz.getSuperclass();
        // 这个标记需要修改目标类  "&& superClazz.isAnnotationPresent(ProtoSuper.class)"
        if (superClazz != null) {
            fields.addAll(getFields(superClazz));
        }
        return fields;
    }


    // -------------------------

    public static Map<Integer, Integer> getToMessageModel(Pair<?, ? extends Message.Builder> pair) {
        return toMessageMap.computeIfAbsent(pair, ReflectAsmManager::buildToMessageModel);
    }

    private static Map<Integer, Integer> buildToMessageModel(Pair<?, ? extends Message.Builder> pair) {
        System.out.println("buildToMessageModel = " + pair);
        final Class<?> pojoClass = pair.key;
        final Class<? extends Message.Builder> messageBuilderClass = pair.value;
        final MethodAccess pojoAccess = getMethodAccess(pojoClass);
        final MethodAccess messageBuilderAccess = getMethodAccess(messageBuilderClass);

        // TODO 同上，加入更多的注解控制逻辑

        final List<Field> fields = getFields(pojoClass);
        Map<Integer, Integer> indexMap = new HashMap<>();
        for (Field field : fields) {
            if (Modifier.isStatic(field.getModifiers())) continue;

            final String pojoGetName = FieldUtils.createDomainGetterName(field.getType(), field.getName());
            try {
                final Method pojoGetMethod = pojoClass.getMethod(pojoGetName);
                final String messageSetName = FieldUtils.createProtobufSetterName(messageBuilderClass, field.getName());
                final Method messageBuilderSetMethod = messageBuilderClass.getMethod(messageSetName, field.getType());

                indexMap.put(pojoAccess.getIndex(pojoGetMethod.getName()),
                        messageBuilderAccess.getIndex(messageBuilderSetMethod.getName()));
            } catch (NoSuchMethodException e) {
                // continue
            }
        }
        return indexMap;
    }

    @Deprecated
    public static <F, T extends Message> void copyProperties(F from, T to) {
        MethodAccess fromMethodAccess = getMethodAccess(from.getClass());
        MethodAccess toMethodAccess = getMethodAccess(to.getClass());
        Field[] fromDeclaredFields = from.getClass().getDeclaredFields();
        for (Field field : fromDeclaredFields) {
            String name = field.getName();
            try {
                Object value = fromMethodAccess.invoke(from, "get" + StringUtils.capitalize(name));
                toMethodAccess.invoke(to, "set" + StringUtils.capitalize(name), value);
            } catch (Exception e) {
                // 设置异常，可能会没有对应字段，忽略
            }
        }
    }

    @Deprecated
    public static <F extends Message, T> void copyProperties(F from, T to) {
        MethodAccess fromMethodAccess = getMethodAccess(from.getClass());
        MethodAccess toMethodAccess = getMethodAccess(to.getClass());
        Field[] fromDeclaredFields = from.getClass().getDeclaredFields();
        for (Field field : fromDeclaredFields) {
            if (Modifier.isStatic(field.getModifiers())) continue;
            String name = field.getName();
            // 对于 message 编译结果的特殊处理
            name = name.substring(0, name.length() - 1);
            try {
                Object value = fromMethodAccess.invoke(from, "get" + StringUtils.capitalize(name));
                toMethodAccess.invoke(to, "set" + StringUtils.capitalize(name), value);
            } catch (Exception e) {
                // 设置异常，可能会没有对应字段，忽略
            }
        }
    }

}
